當API規模慢慢擴大,Unit test變得很重要,可以幫助我們檢查原本已經正常的功能,當開發新Feature的時候,可能改寫function,導致我們沒注意到的地方產生錯誤,原本寫好的Unit test就能幫我們找出該錯誤,今天會分別撰寫Controller, Service及Repository的Test,那麼接下來就開始吧.
$ sail artisan make:test PostRepositoryTest --unit
createPost()
$title
, $content
/**
* 建立文章
*
* @param string $title 標題
* @param string $content 內文
* @return mixed
*/
public function createPost(string $title, string $content)
{
$user = Auth::guard('api')->user();
$post = new Post();
$post->title = $title;
$post->content = $content;
$post->user_id = $user->id;
$post->save();
return $post;
}
phpunit.xml
把這兩行原本註解拿掉,我們要另外用另一個資料庫去測試,才不會影響到我們原本資料庫的資料,記得重啟container!<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
use HasFactory
# database/factories/UserFactory
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = User::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name(),
'account' => $this->faker->unique()->safeEmail(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'enabled' => 1,
];
}
}
use RefreshDatabase
用來清空資料庫的資料,確保每次測試資料不被資料庫數據影響<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tymon\JWTAuth\JWTGuard;
use Mockery;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Repositories\PostRepository;
class PostRepositoryTest extends TestCase
{
use RefreshDatabase;
/**
* @var PostRepository
*/
protected $post_repository;
/**
* 在每個 test case 開始前執行.
*/
public function setUp(): void
{
parent::setUp();
$this->post_repository = app()->make(PostRepository::class);
$this->user = User::factory()->create();
$this->guard_mock = Mockery::mock(JWTGuard::class);
Auth::shouldReceive('guard')
->with('api')
->andReturn($this->guard_mock);
$this->guard_mock->shouldReceive('user')
->andReturn($this->user);
}
/**
* 測試 成功建立文章
*/
public function testCreatePostShouldSuccess()
{
$title = "測試標題";
$content = "測試內文";
$this->post_repository->createPost($title, $content);
$this->assertDatabaseHas('posts', [
'user_id' => $this->user->id,
'title' => $title,
'content' => $content,
]);
}
}
$ sail artisan make:test PostServiceTest --unit
create()
$data
/**
* 建立文章
* @param array $data
* @return mixed
*/
public function create(array $data)
{
$title = Arr::get($data, 'title');
$content = Arr::get($data, 'content');
$post = $this->post_repository->createPost($title, $content);
return $post;
}
use HasFactory
# database/factories/PostFactory
<?php
namespace Database\Factories;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Post::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'title' => $this->faker->word(),
'content' => $this->faker->word(),
'user_id' => User::factory()->create()->id,
];
}
}
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Mockery;
use App\Services\PostService;
use App\Repositories\PostRepository;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PostServiceTest extends TestCase
{
use RefreshDatabase;
/**
* @var PostRepository
*/
protected $post_repository_mock;
/**
* @var PostService
*/
protected $post_service;
/**
* 在每個 test case 開始前執行.
*/
public function setUp(): void
{
parent::setUp();
$this->post_repository_mock = Mockery::mock(PostRepository::class);
$this->post_service = new PostService($this->post_repository_mock);
}
/**
* 測試 建立文章Service處理成功
*/
public function testCreatePostSuccess()
{
$post = Post::factory()->create();
$fake_input = [
'title' => '測試標題',
'content' => '測試內文',
];
$this->post_repository_mock
->shouldReceive('createPost')
->once()
->andReturn($post);
$actual_result = $this->post_service->create($fake_input);
$this->assertEquals($post->title, $actual_result['title']);
$this->assertEquals($post->content, $actual_result['content']);
$this->assertEquals($post->user_id, $actual_result['user_id']);
}
}
$ sail artisan make:test PostControllerTest
# routes/api.php
Route::group(['prefix' => 'auth'], function () {
Route::post('login', [AuthController::class, 'login'])->name('auth.login');
Route::post('register', [AuthController::class, 'register'])->name('auth.register');
});
Route::middleware(['jwt.auth'])->group(function () {
Route::group(['prefix' => 'user'], function () {
Route::get('/', [UserController::class, 'index'])->name('user.index');
});
Route::group(['prefix' => 'post'], function () {
Route::post('/', [PostController::class, 'create'])->name('post.create');
Route::get('/', [PostController::class, 'index'])->name('post.index');
});
});
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tymon\JWTAuth\Facades\JWTAuth;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
/**
* 產生 jwt 驗證 token
*
* @return mixed
*/
protected function createToken($user)
{
$token = JWTAuth::fromUser($user);
return 'Bearer '.$token;
}
}
createToken()
藉此產生token<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use App\Models\User;
use Tests\TestCase;
class PostControllerTest extends TestCase
{
use RefreshDatabase;
/**
* @var API Header
*/
protected $header = [
'X-Requested-With' => 'XMLHttpRequest',
'Content-Type' => 'application/json',
];
public function setUp(): void
{
parent::setUp();
$user = User::factory()->create();
$this->header["Authorization"] = $this->createToken($user);
}
/**
* 測試 建立文章成功
*/
public function testCreatePostSuccess()
{
$fake_data = [
'title' => '測試標題',
'content' => '測試內文',
];
$response = $this->withHeaders($this->header)->postJson(Route('post.create'), $fake_data)->decodeResponseJson();
$this->assertTrue($response['title'] == $fake_data['title']);
$this->assertTrue($response['content'] == $fake_data['content']);
}
}
$ sail test